-- LPCValue
-- Create by weism
-- 对应于LPC的数据结构

module("LPC", package.seeall);

require("core/lpc/Array");

-- 7种数据类型
local UNDEFINED  = 0;
local INT        = 2;
local FLOAT      = 3;
local STRING     = 4;
local BUFFER     = 5;
local ARRAY      = 6;
local MAPPING    = 7;

-- 内部函数声明
local saveType;
local getSaveBinaryIntSize;
local restoreToNumber;
local restoreToString;
local restoreToArray;
local restoreToTable;

-- 计算数值对的元素数量
function count_table(m)
    local i = 0;
    for _, __ in pairs(m) do i = i + 1; end
    return i;
end

-- 将数据转换为LPCValue表示的二进制串
function saveBuffer(buffer, offset, lpc)
    local len = 0;
    if type(lpc) == "number" then
        -- 整数的序列化
        lpc, _ = math.modf(lpc);
        len =  saveType(INT, lpc, buffer, offset);
        offset = offset + len;
    elseif type(lpc) == "string" then
        -- 字符串的序列化
        len = saveType(STRING, string.len(lpc), buffer, offset);
        offset = offset + len;

        Buffer.setstr(buffer, offset, string.len(lpc), lpc);
        len = len + string.len(lpc);
        offset = offset + len;
    elseif (lpc.e and lpc.array) or is_array(lpc) then
        -- 数组
        if lpc.e and lpc.array then
            lpc = lpc.e;
        end
        len = saveType(ARRAY, #lpc, buffer, offset);
        offset = offset + len;
        for i = 1, #lpc do
            local l = saveBuffer(buffer, offset, lpc[i]);
            len = len + l;
            offset = offset + l;
        end
    elseif type(lpc) == "table" then
        -- 数值对
        len = saveType(MAPPING, count_table(lpc), buffer, offset);
        offset = offset + len;
        for k, v in pairs(lpc) do
            assert(type(k) == "number" or type(k) == "string", "key只能是数值或字符串   ");

            -- 先打包key
            local l = saveBuffer(buffer, offset, k);
            len = len + l;
            offset = offset + l;

            -- 再打包value
            l = saveBuffer(buffer, offset, v);
            len = len + l;
            offset = offset + l;
        end
    elseif type(lpc) == "nil" then
        len = saveType(UNDEFINED, 0, buffer, offset);
    else
        assert(false, "不支持的类型：  " .. type(lpc));
    end

    return len;
end

-- 把二进制串还原为LPCValue
function restoreBuffer(buffer, offset)
    local len = 0;
    local value;

    -- 获取类型和数据大小
    local type = Buffer.get8(buffer, offset);
    local size = bit.band(type, 0x0F);
    type = bit.brshift(type, 4);
    len = len + 1;
    offset = offset + 1;

    -- 看是否需要获得额外的数据
    local data = 0;
    if size > 0 then
        if bit.band(Buffer.get8(buffer, offset), 0x80) ~= 0 then
            data = -1;
        else
            data = 0;
        end
        for i = 1, size do
            data = math.pow(2, 8) * data + Buffer.getu8(buffer, offset + i - 1);
        end

        offset = offset + size;
        len = len + size;
    end

    -- 根据各种数据类型恢复数据
    if type == UNDEFINED then
        value = nil;
    elseif type == INT then
        value = data;
    elseif type == STRING then
        value = Buffer.getstr(buffer, offset, data);
        len = len + data;
    elseif type == ARRAY then
        value = {};
        for i = 1, data do
            local v2, l2 = restoreBuffer(buffer, offset);
            len = len + l2;
            offset = offset + l2;
            table.insert(value, v2);
        end
    elseif type == MAPPING then
        value = {};
        for i = 1, data do
            -- key
            local k, l2 = restoreBuffer(buffer, offset);
            len = len + l2;
            offset = offset + l2;

            -- value
            local v, l2 = restoreBuffer(buffer, offset);
            len = len + l2;
            offset = offset + l2;
            value[k] = v;
        end

    elseif type == BUFFER then
        value = Buffer.getstr(buffer, offset, data);

        len = len + data;
    end

    -- 返回读取的长度和数据
    return  value, len;
end

-- 保存数据类型字段
saveType = function(type, len, buffer, offset)
    assert(type >= 0 and type < 15, "参数type错误  ");

    local size = getSaveBinaryIntSize(len);
    assert(size <= 15, "int长度不对。  ");

    -- 打包第一个字节
    local byte1 = bit.band(bit.blshift(type, 4), 255);
    Buffer.set8(buffer, offset, bit.bor(byte1, size));

    -- 打包长度
    for i = size, 1, -1 do
        Buffer.set8(buffer, offset + i, bit.band(len, 255));
        len = bit.brshift(len, 8);
    end

    -- 返回保存了多少的长度
    return size + 1;
end

-- 保存为字符串
function saveString(lpc)
    local valueType = type(lpc);
    if valueType == "nil" then
        return "(undefined)";
    elseif valueType == "number" then
        return tostring(lpc);
    elseif valueType == "string" then
        lpc = string.gsub(lpc, "\\", "\\\\");
        lpc = string.gsub(lpc, "\"", "\\\"");
        lpc = string.gsub(lpc, "\n", "\r");
        return string.format("\"%s\"", lpc);
    elseif valueType == "table" then
        local isArray = false;
        if lpc.e and lpc.array then
            lpc = lpc.e;
            isArray = true;
        end
        if isArray or is_array(lpc) then
            --array
            local prefix = "({";
            local result = {};
            for i = 1, #lpc do
                table.insert(result, saveString(lpc[i]));
            end
            table.insert(result, "})");
            return prefix .. table.concat(result, ",");
        else
            --mapping
            local prefix = "([";
            local result = {};
            for k, v in pairs(lpc) do
                table.insert(result, string.format("%s:%s", saveString(k), saveString(v)));
            end
            table.insert(result, "])");
            return prefix .. table.concat(result, ",");
        end
    end
end

-- 从字符串中还原
function restoreString(lpc, offset)
    if not lpc or lpc == "" then
        return "";
    end

    if not offset then offset = 1; end
    local first = string.sub(lpc, offset, offset);
    if first == '"' then
        -- 还原为字符串
        offset = offset + 1;
        return restoreToString(lpc, offset);
    elseif first == '(' then
        offset = offset + 1;
        local second = string.sub(lpc, offset, offset);
        if second == '{' then
            -- 这是数组
            offset = offset + 1;
            return restoreToArray(lpc, offset);
        elseif second == '[' then
            -- 这是数值对
            offset = offset + 1;
            return restoreToTable(lpc, offset);
        end
    elseif first == '-' or (first >= '0' and first <= '9') then
       -- 还原为整数
       return tonumber(lpc), offset;
    end
end

-- 整数打包的长度
getSaveBinaryIntSize = function(i)
    if i == 0 then
        return 0;
    end

    local len = 1;
    while i < -128 or i > 127 do
        i, _ = math.modf(i / 256);
        len = len + 1;
    end

    return len;
end

-- 转换为数字类型
restoreToNumber = function(lpc, offset)
    local count = 0;
    local start = offset;
    while offset <= string.len(lpc) do
        local c = string.sub(lpc, offset, offset);
        if c == "-" or (c >= "0" and c <= "9") then
            count = count + 1;
            offset = offset + 1;
        else
            break;
        end
    end

    local n = string.sub(lpc, start, start + count - 1);
    return tonumber(n), offset;
end

-- 转换为字符串
restoreToString = function(lpc, offset)
    local count = 0;
    local start = offset;
    while offset <= string.len(lpc) do
        local c = string.sub(lpc, offset, offset);
        if c == '"' then
            offset = offset + 1;
            break;
        elseif c == '\\' then
            offset = offset + 2;
            count = count + 2;
        else
            offset = offset + 1;
            count = count + 1;
        end
    end

    local result = string.sub(lpc, start, start + count - 1);
    result = string.gsub(result, "\\\"", "\"");
    result = string.gsub(result, "\\\\", "\\");
    result = string.gsub(result, "\r", "\n");
    return result, offset;
end

-- 还原为数组
restoreToArray = function(lpc, offset)
    local arr = {};
    local v;
    while offset <= string.len(lpc) do
        local first = string.sub(lpc, offset, offset);
        if first == '"' then
            offset = offset + 1;
            v, offset = restoreToString(lpc, offset);
            table.insert(arr, v);
        elseif first == '(' then
            offset = offset + 1;
            local second = string.sub(lpc, offset, offset);
            if second == "{" then
                offset = offset + 1;
                v, offset = restoreToArray(lpc, offset);
                table.insert(arr, v);
            elseif second == "[" then
                offset = offset + 1;
                v, offset = restoreToTable(lpc, offset);
                table.insert(arr, v);
            end
        elseif first == '-' or (first >= '0' and first <= '9') then
            v, offset = restoreToNumber(lpc, offset);
            table.insert(arr, v);
        elseif first == '}' then
            offset = offset + 1;
            if string.sub(lpc, offset, offset) == ")" then
                -- 完成了
                offset = offset + 1;
                return arr, offset;
            end
        else
            offset = offset + 1;
        end
    end
end

-- 还原为table
restoreToTable = function(lpc, offset)
    local m = {};
    local v;
    local parseKey = true;
    local currKey;
    while offset <= string.len(lpc) do
        local first = string.sub(lpc, offset, offset);
        if first == '"' then
            offset = offset + 1;
            v, offset = restoreToString(lpc, offset);
            if parseKey then
                currKey = v;
                parseKey = true;
            else
                m[currKey] = v
            end
        elseif first == ':' then
            offset = offset + 1;
            parseKey = false;
        elseif first == '-' or (first >= '0' and first <= '9') then
            v, offset = restoreToNumber(lpc, offset);
            if parseKey then
                currKey = v;
                parseKey = true;
            else
                m[currKey] = v
            end
        elseif first == '(' then
            offset = offset + 1;
            local second = string.sub(lpc, offset, offset);
            if second == '{' then
                offset = offset + 1;
                v, offset = restoreToArray(lpc, offset);
                m[currKey] = v;
            elseif second == '[' then
                offset = offset + 1;
                v, offset = restoreToTable(lpc, offset);
                m[currKey] = v;
            else offset = offset + 1;
            end
        elseif first == ']' then
            offset = offset + 1;
            if string.sub(lpc, offset, offset) == ')' then
                offset = offset + 1;
                return m, offset;
            end
        else
            offset = offset + 1;
            parseKey = true;
        end
    end
end


-- 测试接口
function test()
    local buffer = Buffer.create(0);
    saveBuffer(buffer, 1, -123);
    assert(Buffer.toHex(buffer) == "21 85", "saveBuffer(int)出错" .. Buffer.toHex(buffer));

    local v = restoreBuffer(buffer, 1);
    assert(v == -123, "restoreBuffer(int)出错" .. v);

    buffer = Buffer.create(0);
    saveBuffer(buffer, 1, 123345);
    assert(Buffer.toHex(buffer) == "23 01 E1 D1", "saveBuffer(int)出错" .. Buffer.toHex(buffer));

    v = restoreBuffer(buffer, 1);
    assert(v == 123345, "restoreBuffer(int)出错" .. v);

    buffer = Buffer.create(0);
    saveBuffer(buffer, 1, "hello world");
    assert(Buffer.toHex(buffer) == "41 0B 68 65 6C 6C 6F 20 77 6F 72 6C 64", "saveBuffer(int)出错" .. Buffer.toHex(buffer));

    v = restoreBuffer(buffer, 1);
    assert(v == "hello world", "restoreBuffer(string)出错" .. v);

    -- LPC数组
    Array.test();

    -- array
    buffer = Buffer.create(0);
    local arr = Array.new();
    arr:insert(1, 1);
    arr:insert(2, 2);
    arr:insert(3, 3);
    saveBuffer(buffer, 1, arr);
    assert(Buffer.toHex(buffer) == "61 03 21 01 21 02 21 03", "saveBuffer(array)出错" .. Buffer.toHex(buffer));

    v = restoreBuffer(buffer, 1);
    assert(v:size() == 3 and v:get(1) == 1 and v:get(2) == 2 and v:get(3) == 3, "restoreBuffer(array)出错");

    -- 普通的数值对
    buffer = Buffer.create(0);
    saveBuffer(buffer, 1, { a = 1 });
    assert(Buffer.toHex(buffer) == "71 01 41 01 61 21 01", "saveBuffer(table)出错" .. Buffer.toHex(buffer));

    v = restoreBuffer(buffer, 1);
    assert(v["a"] == 1, "restoreBuffer(table)出错");

    -- 测试saveString接口
    assert(saveString(123) == "123", "saveString(int)出错");

    assert(restoreString("123") == 123, "失败");
    assert(restoreString('"123"') == "123", "失败");
    v = restoreString("({1,2,3})");
    assert(v:get(1) == 1 and v:get(2) == 2 and v:get(3) == 3, "失败");
    v = restoreString('(["a":2])');
    assert(v["a"] == 2, "失败");

--    t = restoreString("([\"todoMessages\":({([1:\"CMD_PET_LEVEL_UP\",2:([\"add_level\":1,\"class_id\":2,]),]), }),\"messages\":({}),\"sync\":42,])");
--    printTable(t.todoMessages:toTable());
--    printTable(t.messages:toTable());

    print("*** LPCValue功能测试通过 ***")
end
